#!/usr/local/easyops/python/bin/python
# -*- coding: utf-8 -*-

"""
网络设备接口信息采集。
"""

from imp import reload
import json
import os
import sys
import traceback
import re

# 自动采集脚本无法引用lib，手动拷贝引用
sys.path.insert(0, os.path.join("/usr/local/easyops", "agent/plugins"))

from ianbai_lib.auto_collect import AutoDiscovery
from ianbai_lib.cmdb import CMDBResource
from ianbai_lib.log import get_logger
from ianbai_lib.auto_collect import snmp_walker


reload(sys)
sys.setdefaultencoding("utf-8")


# 配置
# 接口模型ID
NETD_PORT_MODEL_ID = "NETD_INTERFACE"
# 网络设备到接口的关系ID
NETD_PORT_REL_ID = "NETD_INTERFACE"
# 接口到接口关系ID
PORT_PORT_REL_ID = "NETD_INTERFACE_down"


logger = get_logger(__name__, "INFO")


def format_phyadd(str):
    """格式化物理地址。"""
    res = ""
    if str:
        tmp = str.split(":")
        res = []
        for i in tmp:
            res.append(i.rjust(2, "0"))
        res = ":".join(res)
        return res.upper()
    return res


def format_dec_mac(str):
    """格式化十进制MAC地址。"""
    res = ""
    if str:
        res = ":".join(map(lambda x: hex(int(x))[2:].zfill(2).upper(), str.split(".")))
    return res


def get_if_table(callSnmpwalk):
    """获取接口基本信息信息，返回以索引号为键的字典。"""

    # 参考:http://oidref.com/1.3.6.1.2.1
    # 接口的各类信息可能出现空值和无的情况,需要在索引号维度聚合
    ifIndexOid = ".1.3.6.1.2.1.2.2.1.1"
    ifDescrOid = ".1.3.6.1.2.1.2.2.1.2"
    ifTypeOid = ".1.3.6.1.2.1.2.2.1.3"
    ifMtuOid = ".1.3.6.1.2.1.2.2.1.4"
    ifSpeedOid = ".1.3.6.1.2.1.2.2.1.5"
    ifPhysAddressOid = ".1.3.6.1.2.1.2.2.1.6"
    ifAdminStatusOid = ".1.3.6.1.2.1.2.2.1.7"
    ifOperStatusOid = ".1.3.6.1.2.1.2.2.1.8"
    ifNameOid = ".1.3.6.1.2.1.31.1.1.1.1"
    ifAliasOid = ".1.3.6.1.2.1.31.1.1.1.18"

    ifTmp = {}
    
    def __get_if(str):
        """将接口信息转换为以索引号为键的字典。"""
        # 各项接口信息的输出并不与索引号完全对齐,必须根据索引号进行匹配组合
        rec = re.compile(r".*\.(\d*) .*: (.*)", re.M)
        res = re.findall(rec, str)
        return dict(res)
    
    def __del_brackets(str):
        """删除行尾行尾圆括号。"""
        rec = re.compile(r"\(.*\)$", re.M)
        res = re.sub(rec, "", str)
        return res

    # IF-MIB::ifDescr.16777216 = STRING: fc1/1
    cal = callSnmpwalk(ifDescrOid)
    ifTmp["ifDescr"] = __get_if(cal)

    # IF-MIB::ifType.16777216 = INTEGER: fibreChannel(56)
    cal = callSnmpwalk(ifTypeOid)
    cal = __del_brackets(cal)
    ifTmp["ifType"] = __get_if(cal)

    # IF-MIB::ifMtu.16777216 = INTEGER: 2112
    cal = callSnmpwalk(ifMtuOid)
    ifTmp["ifMtu"] = __get_if(cal)

    # IF-MIB::ifSpeed.16777216 = Gauge32: 4294967295
    cal = callSnmpwalk(ifSpeedOid)
    ifTmp["ifSpeed"] = __get_if(cal)

    # IF-MIB::ifPhysAddress.16777216 = STRING: 20:1:0:de:fb:94:e:20
    cal = callSnmpwalk(ifPhysAddressOid)
    ifTmp["ifPhysAddress"] = __get_if(cal)

    # IF-MIB::ifAdminStatus.16777216 = INTEGER: up(1)
    cal = callSnmpwalk(ifAdminStatusOid)
    cal = __del_brackets(cal)
    ifTmp["ifAdminStatus"] = __get_if(cal)

    # IF-MIB::ifOperStatus.16777216 = INTEGER: up(1)
    cal = callSnmpwalk(ifOperStatusOid)
    cal = __del_brackets(cal)
    ifTmp["ifOperStatus"] = __get_if(cal)

    # IF-MIB::ifName.1 = STRING: Gi0/0
    cal = callSnmpwalk(ifNameOid)
    ifTmp["ifName"] = __get_if(cal)

    # IF-MIB::ifAlias.3 = STRING: TO-CHENHAIRUO-[lianghua]
    cal = callSnmpwalk(ifAliasOid)
    ifTmp["ifAlias"] = __get_if(cal)

    # 合并接口信息
    # IF-MIB::ifIndex.16777216 = INTEGER: 16777216
    cal = callSnmpwalk(ifIndexOid)
    rec = re.compile(r".*: (\d*)", re.M)
    res = re.findall(rec, cal)
    ifIndex = res

    ifInfo = {}
    for index in ifIndex:
        ifInfo[index] = {
            # 保留接口索引，作为合并mac地址表和邻居信息的依据
            "ifIndex": index,
            "ifDescr": ifTmp["ifDescr"].get(index, ""),
            "ifType": ifTmp["ifType"].get(index, ""),
            "ifMtu": ifTmp["ifMtu"].get(index) and int(ifTmp["ifMtu"].get(index)),
            "ifSpeed": ifTmp["ifSpeed"].get(index) and int(ifTmp["ifSpeed"].get(index)),
            "ifPhysAddress": format_phyadd(ifTmp["ifPhysAddress"].get(index, "")),
            "ifAdminStatus": ifTmp["ifAdminStatus"].get(index, "").upper(),
            "ifOperStatus": ifTmp["ifOperStatus"].get(index, "").upper(),
            "ifName": ifTmp["ifName"].get(index, ""),
            "ifAlias": ifTmp["ifAlias"].get(index, ""),
        }

    return ifInfo


# 原理:
# - mac表/arp表合并
# - 类思科（包含浪思）设备，在snmp1/2c版本下必须使用拼接的团体字(community@vlanId)在vlan下获取arp相关的映射
#   关系，在snmp3版本下需指定上下文“vlan-*”以匹配并返回所有vlan下的映射关系（未验证）
#   参考：https://www.cisco.com/c/en/us/support/docs/ip/simple-network-management-protocol-snmp/40367-camsnmp40367.html
#   参考：https://community.cisco.com/t5/network-management/vlan-bridge-mib-and-snmpv3-contexts/td-p/1589698
# - 如何获得设备当前使用的vlan，可以通过类似思科vtp的私有oid，但建议在ifDescr输出中获取已经配置接口的vlan
# - 查询arp表可以获得接口索引/ip/mac，但接口均为逻辑接口，不包含物理接口，因此只能作为根据mac查询ip的字典
# - mac地址映射到网桥接口，网桥接口映射到if接口


def re_search(rec, str):
    res = re.search(rec, str)
    if res:
        return res.group(1)
    else:
        return ""


def get_if_arp_table(callSnmpwalk, snmpVer, vender, community=None):
    """获取间arp表。
    
    参数：
    - callSnmpwalk：采集器；
    - snmpVer：snmp版本；
    - vender：厂商；
    - community：snmp1/2c时使用的团体字；
    """
    
    # 网桥接口到真实接口的映射，示例中17为网桥接口索引，19为真实接口索引
    # SNMPv2-SMI::mib-2.17.1.4.1.2.17 = INTEGER: 19
    dot1dBasePortIfIndexOid = ".1.3.6.1.2.1.17.1.4.1.2"
    # mac地址到网桥接口的映射，示例1中4.49.16.43.253.174为mac地址的十进制表示，576为网桥地址
    # 此项输出在不同设备中表现不一致，示例2中则包含了其余部分，因此需要从=号向前匹配6组
    # 1: SNMPv2-SMI::mib-2.17.4.3.1.2.4.49.16.43.253.174 = INTEGER: 576
    # 2: SNMPv2-SMI::mib-2.17.4.3.1.2.162.6.0.80.86.134.178.150 = INTEGER: 54
    dot1dTpFdbPortOid = ".1.3.6.1.2.1.17.4.3.1.2"
    # ip地址到mac地址的映射(arp)，示例中10.60.164.71为ip地址，0:50:56:86:7a:b6为mac地址
    # IP-MIB::ipNetToMediaPhysAddress.164.10.60.164.71 = STRING: 0:50:56:86:7a:b6
    ipNetToMediaPhysAddressOid  = ".1.3.6.1.2.1.4.22.1.2"

    # [("17", "19"), ...] -> (网桥，接口)
    dot1dBasePortIfIndexDatas = []
    # [("40.199.206.223.158.174", "2392"), ...] -> (mac，网桥)
    dot1dTpFdbPortDatas = []
    # [("10.60.164.221", "28:c7:ce:df:9e:c7"), ...] -> (ip，mac)
    ipNetToMediaPhysAddressDatas = []

    def __get_dot1dBasePortIfIndex_dot1dTpFdbPort(walker, community=None, context=None):
        """获取网桥接口到真实接口的映射、mac地址到网桥接口的映射。"""

        paras = {}
        if community: paras["community"] = community
        if context: paras["context"] = context

        cal = walker(dot1dBasePortIfIndexOid, **paras)

        # 如果INTEGER后的值为空，则此行取值为空
        rec = re.compile(r".*\.(\d+) = INTEGER: (\d+)", re.M)
        res = re.findall(rec, cal)
        dot1dBasePortIfIndexDatas.extend(res)

        cal = walker(dot1dTpFdbPortOid, **paras)

        # 取=前6组数字
        rec = re.compile(r"\.((?:\.?\d+){6}) = INTEGER: (\d+)", re.M)
        res = re.findall(rec, cal)
        dot1dTpFdbPortDatas.extend(res)

    # 根据制造商决定如何获取映射信息
    # 目前了解思科存在此差异，浪思因为使用思科系统，表现与思科一致
    IN_VLAN_VENDER = {
        "9": "cisco",      # 思科
        "48797": "INOS",   # 浪潮思科
    }

    if vender in IN_VLAN_VENDER:
        # 获取团体字列表
        ifDescrOid = ".1.3.6.1.2.1.2.2.1.2"
        ifOperStatusOid = ".1.3.6.1.2.1.2.2.1.8"

        # 获取接口索引为键,vlanId为值的字典
        cal = callSnmpwalk(ifDescrOid)
        # 需要根据指定厂商设备的实际输出做适配
        rec = re.compile(r"(\d+) = STRING: Vlan(\d+)$", re.M)
        vlanIds = dict(re.findall(rec, cal))

        # 获取操作状态为up的接口索引
        cal = callSnmpwalk(ifOperStatusOid)
        rec = re.compile(r"(\d+) = INTEGER: up.*$", re.M)
        upIfIndex = re.findall(rec, cal)

        # 根据snmp协议版本决定如何获取映射信息
        if snmpVer in ["1", "2c"]:
            # 获取可用状态的vlanId并构建团体字列表
            communities = [community]
            communities.extend(["%s@%s" % (community, v) for k, v in vlanIds.items() if k in upIfIndex])
            
            for community in communities:
                # 指定设备确定支持,但部分vlan下可能无此oid
                __get_dot1dBasePortIfIndex_dot1dTpFdbPort(callSnmpwalk, community=community)
        else:
            # snmp3需更新context参数的值，以获取全部信息
            # TODO：找机会验证
            __get_dot1dBasePortIfIndex_dot1dTpFdbPort(callSnmpwalk, context="vlan-*")
    else:
        __get_dot1dBasePortIfIndex_dot1dTpFdbPort(callSnmpwalk)
    
    # 获取arp信息
    cal = callSnmpwalk(ipNetToMediaPhysAddressOid)
    rec = re.compile(r"\d+\.([\.\d]+) = STRING: (.*)", re.M)
    ipNetToMediaPhysAddressDatas.extend(re.findall(rec, cal))

    # 合并信息
    # 以网桥号为键的接口号字典
    dot1dBasePortIfIndexDatasDict = dict(dot1dBasePortIfIndexDatas)
    # 以mac地址为键的网桥号字典
    dot1dTpFdbPortDatasDict = dict([(format_dec_mac(x[0]), x[1]) for x in dot1dTpFdbPortDatas])
    # 以mac地址为键的ip字典
    ipNetToMediaPhysAddressDatas = dict([(format_phyadd(x[1]), x[0]) for x in ipNetToMediaPhysAddressDatas])

    # 以接口号为键的ip和mac字典
    datas = {}
    for mac, bridge in dot1dTpFdbPortDatasDict.items():
        ifIndex = dot1dBasePortIfIndexDatasDict.get(bridge)
        
        # 当没有学习到此mac地址,但有关于该mac地址的转发时,对应的网桥索引号为0,找不到对应的接口索引号
        if not ifIndex:
            logger.debug(u"无接口索引号, MAC：%s" % mac)
            continue

        ifData = {
            "peerMac": mac,
            "peerIp": ipNetToMediaPhysAddressDatas.get(mac, "UNKNOWN"),
        }
        if ifIndex not in datas:
            datas[ifIndex] = [ifData]
        else:
            datas[ifIndex].append(ifData)
    
    return datas, dot1dBasePortIfIndexDatasDict


# 原理:
# - 获取对端名称/对端接口/对端描述
# - 对于lldp，需要考虑对端设备标识解码类型，以构建网络设备接口之间的关系，还需要根据网桥接
#   口号找到接口号，对于cdp，输出直接映射到接口索引，不需要根据网桥接口转换
# - lldp设备的表现也存在非标的情况，例如迈普设备获取本端lldpLocPortId，得到的是类似ifName的gi0/1，
#   但是ifName和ifDescr得到的都是类似描述信息的gigabitethernet0/1，而lldpLocPortDesc本应得到本
#   端描述gigabitethernet0/1，但得到未知值TO-ZHOUHAIWEI-ESX(可能是接口备注)，而lldpLocPortNum在
#   迈普设备上为网桥接口号到接口号的映射，但浪思设备并未实现此oid，因此无法通过以上oid得到网桥接口号
#   到接口号的映射，经过检查，获取mac地址表时得到的映射关系通用性较高，此处直接继承
# - 可能出现傻瓜交换机透传lldp报文，导致一个接口下多个设备的情况，暂不考虑
# - 数据验证，查看本端信息，与采集结果对照，验证准确性
#   lldpLocPortEntry: 1.0.8802.1.1.2.1.3.7.1
#   验证过程中发现a终端a1网卡连接交换机a1口，a终端a2网卡连接交换机a2口，但是a1网卡的mac地址出现在交
#   换机a2口的mac表中，此数据从采集的角度看是正确的


def get_lldp_neighbor(callSnmpwalk, bridgeToIfDict, vender=""):
    """获取lldp邻居信息。
    
    参数：
    - callSnmpwalk：采集器；
    - bridgeToIfDict：网桥至接口的字典；
    - vender：厂商代码；

    参考：
    - snmp点位：https://support.huawei.com/enterprise/zh/doc/EDOC1100075299/140891ac
    - 类型取值：https://zhuanlan.zhihu.com/p/548454389
    """

    # 对端设备标识解码类型，3表示mac地址，5表示设备名称
    # 示例中0为时间戳，21为本端网桥接口号，3为对端设备索引
    # iso.0.8802.1.1.2.1.4.1.1.6.0.21.3 = INTEGER: 3
    lldpRemPortIdSubtypeOid = "1.0.8802.1.1.2.1.4.1.1.6"
    # 对端设备名称
    # iso.0.8802.1.1.2.1.4.1.1.9.0.21.3 = STRING: "test163-47"
    lldpRemSysNameOid = "1.0.8802.1.1.2.1.4.1.1.9"
    # 对端接口，根据解码类型，5为对端接口名称
    # iso.0.8802.1.1.2.1.4.1.1.7.0.21.3 = Hex-STRING: 14 18 77 66 70 63
    # iso.0.8802.1.1.2.1.4.1.1.7.0.145.1 = STRING: "gei_6/1"
    lldpRemPortIdOid = "1.0.8802.1.1.2.1.4.1.1.7"
    # 对端系统描述
    # iso.0.8802.1.1.2.1.4.1.1.10.0.21.3 = STRING: "VMware ESX Releasebuild-16316930"
    lldpRemSysDescOid = "1.0.8802.1.1.2.1.4.1.1.10"
    # 对端接口描述
    # iso.0.8802.1.1.2.1.4.1.1.8.0.21.3 = STRING: "port 194379 on dvSwitch dvs-prd (etherswitch)"
    lldpRemPortDescOid = "1.0.8802.1.1.2.1.4.1.1.8"
    # 对端机箱标识解码类型，4为mac地址
    # iso.0.8802.1.1.2.1.4.1.1.4.0.437714944.0 = INTEGER: 4
    lldpRemChassisIdSubtypeOid = "1.0.8802.1.1.2.1.4.1.1.4"
    # 对端机箱标识
    # iso.0.8802.1.1.2.1.4.1.1.5.0.83886080.0 = Hex-STRING: 00 B6 70 25 FF 3B 
    lldpRemChassisIdOid = "1.0.8802.1.1.2.1.4.1.1.5"

    # 以接口索引为键的lldp信息字典
    lldpRemPortIdSubtypeDict = {}
    lldpRemSysNameDict = {}
    lldpRemPortIdDict = {}
    lldpRemSysDescDict = {}
    lldpRemPortDescDict = {}
    lldpRemChassisIdSubtypeDict = {}
    lldpRemChassisIdDict = {}

    def __get_info(oid):
        cal = callSnmpwalk(oid)

        rec = re.compile(r"\.((?:\.?\d+){1})\.\d+ = .*: (.*)", re.M)
        res = dict(re.findall(rec, cal))
        
        # 根据厂商决定取值方式
        # lldp索引为接口索引的制造商
        LLDP_INDEX_IS_IF_VENDER = {
            "9": "cisco", # 思科
        }

        if vender not in LLDP_INDEX_IS_IF_VENDER:
            res = {bridgeToIfDict.get(k, "UNKNOWN"): v.strip("\"") for k, v in res.items()}
        else:
            res = {k: v.strip("\"") for k, v in res.items()}
        return res
    
    def __format_mac(str):
        res = ""
        if str:
            res = ":".join(str.split(" ")).strip(":")
        return res
    
    datas = {}
    
    lldpRemPortIdSubtypeDict.update(__get_info(lldpRemPortIdSubtypeOid))
    lldpRemSysNameDict.update(__get_info(lldpRemSysNameOid))
    lldpRemPortIdDict.update(__get_info(lldpRemPortIdOid))
    lldpRemSysDescDict.update(__get_info(lldpRemSysDescOid))
    lldpRemPortDescDict.update(__get_info(lldpRemPortDescOid))
    lldpRemChassisIdSubtypeDict.update(__get_info(lldpRemChassisIdSubtypeOid))
    lldpRemChassisIdDict.update(__get_info(lldpRemChassisIdOid))

    # 合并信息
    # 以接口索引为键的lldp信息字典
    for ifIndex, remSysName in lldpRemSysNameDict.items():
    
        datas[ifIndex] = {
            "remSysName": remSysName,
            "remSysDesc": lldpRemSysDescDict.get(ifIndex),
            "remPortDesc": lldpRemPortDescDict.get(ifIndex),
            "remPortIdType": lldpRemPortIdSubtypeDict.get(ifIndex),
            "remChassisIdType" : lldpRemChassisIdSubtypeDict.get(ifIndex),
            "remPortId": lldpRemPortIdDict.get(ifIndex),
            "remChassisId" : lldpRemChassisIdDict.get(ifIndex),
        }
        
        # 二层只有3/5/7三种类型,3是mac地址,需要格式化
        if datas[ifIndex]["remPortIdType"] == "3":
            datas[ifIndex]["remPortId"] = __format_mac(lldpRemPortIdDict.get(ifIndex))
            
        if datas[ifIndex]["remChassisIdType"] == "4":
            datas[ifIndex]["remChassisId"] = __format_mac(lldpRemChassisIdDict.get(ifIndex))
            
    return datas


AutoDiscovery = AutoDiscovery()


def main(modelId):
    netdObj = CMDBResource(modelId, EASYOPS_ORG)

    query = {
        "isCollect": {"$eq": "是"},
        "collectHost": {"$eq": EASYOPS_LOCAL_IP},
        "snmpVer": {"$exists": True},
        "$or": [
            {"inBandIp": {"$exists": True}},
            {"outBandIp": {"$exists": True}},
        ]
    }
    fields = ["name", "sysName", "inBandIp", "outBandIp", "snmpVer", "snmpInfo2c", "snmpInfo3"]
    
    logger.info(u"开始获取采集对象")

    netds = netdObj.get_all(netdObj.search_v3, query=query, fields=fields)

    logger.info(u"获取成功，对象数量：%s" % len(netds))

    # 上报到接口模型的数据，创建接口实例
    reportPortDatas = []
    # 记录设备到单个接口的映射关系
    netdMapPortDatas = []

    for netd in netds:
        # 优先使用带外IP
        host = netd.get("outBandIp") or netd.get("inBandIp")

        snmpVer = netd.get("snmpVer")

        # 验证必填信息，避免不必要的采集
        # snmp 1或2c
        if snmpVer in ["1", "2c"] and not netd["snmpInfo2c"]["community"]:
            logger.warning(u"团体字为空")
            continue
        
        # snmp 3
        # TODO：补充校验逻辑
        if snmpVer == "3":
            pass

        logger.info(u"正在采集：%s" % host)

        community = netd.get("snmpInfo2c", {}).get("community")
        callSnmpwalk = snmp_walker(host, netd.get("snmpVer"),
                                   community=community,
                                   authProtocol=netd.get("snmpInfo3", {}).get("authProtocol"),
                                   level=netd.get("snmpInfo3", {}).get("level"),
                                   context=netd.get("snmpInfo3", {}).get("context"),
                                   username=netd.get("snmpInfo3", {}).get("username"),
                                   authPasswd=netd.get("snmpInfo3", {}).get("authPasswd"),
                                   privProtocol=netd.get("snmpInfo3", {}).get("privProtocol"),
                                   privPasswd=netd.get("snmpInfo3", {}).get("privPasswd"))

        sysVenderOid = ".1.3.6.1.2.1.1.2.0"
        cal = callSnmpwalk(sysVenderOid)
        rec = re.compile(r".*enterprises\.([0-9]+)\.", re.I)
        vender = re_search(rec, cal)

        if not vender:
            logger.warning(u"厂商代码获取失败，按照默认方式执行采集")
        
        # 初始化
        ifTable = {}
        ifArpTable = {}
        bridgeToIfDict = {}
        lldpNeighborTable = {}

        try:
            ifTable = get_if_table(callSnmpwalk)
            logger.debug("%s\n%s" % (netd["sysName"], json.dumps(ifTable)))

            ifArpTable, bridgeToIfDict = get_if_arp_table(callSnmpwalk, snmpVer, vender, community)
            logger.debug("%s\n%s" % (netd["sysName"], json.dumps(ifArpTable)))
            logger.debug("%s\n%s" % (netd["sysName"], json.dumps(bridgeToIfDict)))

            lldpNeighborTable = get_lldp_neighbor(callSnmpwalk, bridgeToIfDict, vender)
            logger.debug("%s\n%s" % (netd["sysName"], json.dumps(lldpNeighborTable)))
        except Exception as e:
            logger.error(u"意料之外的错误：%s" % e)
            logger.debug(u"错误详情：%s" % traceback.format_exc())
            continue
        
        for ifIndex, data in ifTable.items():
            neighbor = lldpNeighborTable.get(ifIndex)
            data["arps"] = ifArpTable.get(ifIndex, [])
            data["neighbors"] = [neighbor] if neighbor else []
            # 接口名称
            # 路由器接口名称可能存在重复,接口描述是否重复不能保证,因此在名称中增加接口索引
            # 为便于按照接口名称搜索,使用_替换名称中的空字符
            # 为增强接口名称可识别性,使用|做连接符
            data["name"] = "%s|%s|%s" % (netd["sysName"], data["ifIndex"], data["ifName"])
            data["name"] = data["name"].replace(" ", "_").strip()
            # 用于构建端口关系的本端标识
            data["localIdent"] = "%s|%s" % (netd["sysName"], data["ifName"])

            # 建立接口间关系
            # 识别对端接口存在复杂情况：
            # 以sysName+ifName识别：
            # - sysName存在不一致情况,例如lldp中的sysName可能带有域名样式的后缀
            # - ifName存在不一致情况,对端接口可能是全称或者简称
            # sysName+ifName+ifIndex识别：
            # - ifIndex虽然在协议中定义，但往往查不到
            # ifPhysAddress识别：
            # - 在限定对端类别的情况下可以通过ifPhysAddress标识，但当设备为三层交换机时可能所有的接口具备相同的mac地址
            # 综上，暂用sysName+ifName作为标识，特殊情况可能需要促使客户进行数据治理

            # remPortIdType为5或7一般连接到网络设备,为3一般连接到终端
            # 对端是国产网络设备时,可能表现与终端类似
            #if neighbor and (neighbor["remPortIdType"] == "5" or neighbor["remPortIdType"] == "7") and neighbor["remChassisIdType"] == "4":
            #    data[PORT_PORT_REL_ID] = [{
            #        "ifPhysAddress": neighbor["remChassisId"]
            #    }]

            # 使用sysName+ifName作端口关联
            if neighbor and neighbor["remPortIdType"] == "5":
                data[PORT_PORT_REL_ID] = [{
                    "localIdent": "%s|%s" % (neighbor["remSysName"], neighbor["remPortId"])
                }]
            
            reportPortDatas.append(data)
            
            # 网络设备上报数据,建立网络设备与接口的关系
            netdMapPortDatas.append({
                "sysName": netd["sysName"],
                NETD_PORT_REL_ID: [{"name": data["name"]}]
            })
    
    reportNetdDatas = []
    reportNetdDatasIndexRecord = {}
    for i in netdMapPortDatas:
        sysName = i["sysName"]
        if sysName not in reportNetdDatasIndexRecord:
            reportNetdDatas.append(i)
            reportNetdDatasIndexRecord[sysName] = len(reportNetdDatas) - 1
        else:
            reportNetdDatas[reportNetdDatasIndexRecord[sysName]][NETD_PORT_REL_ID].extend(i[NETD_PORT_REL_ID])

    AutoDiscovery.data_format(jsonData=reportPortDatas, objectId=NETD_PORT_MODEL_ID, pks=["name"], upsert=True)
    AutoDiscovery.data_format(jsonData=reportNetdDatas, objectId=modelId, pks=["sysName"], upsert=False)
    AutoDiscovery.report_data()


if __name__ == "__main__":
    """
    脚本输入参数：
    - inputModelId：模型ID；
    """

    main(inputModelId)
